/**
 * 自定义 Promise 类   标准的 Promise 库有 bluebird、Q
 * Promise 优缺点
 * 优点：1)解决异步嵌套问题 2) 可以解决多个异步并发问题
 * 缺点：1)基于回调 callback 实现 2) Promise 无法终止异步
 * Promise 三个状态：pending(默认->等待) fulfilled(成功) rejected(拒绝)
 * 1) Promise 只能由 pending 变成 fulfilled/rejected 其中一种，不可逆转
 * 2) 创建 Promise 需要传入一个函数(executor)，这个函数内的代码会立即执行
 * 3) executor 有两个参数：成功回调函数参数 resolve 和失败回调函数参数 reject
 * 
 * 扩展：判断类型方法
 * 1) typeof(null 会返回 'object'、函数返回 'function')
 * 2) instanceof(不能判断基本数据类型)
 * 3) constructor(这个可以被修改) 比如String 构造函数的 constructor 修改为 Object
 * 4) Object.prototype.toString.call(data) (精确区分包含 Date、Math等，返回 '[object Xxx]')
 */
const PENDING = 'PENDING';
const RESOLVED = 'RESOLVED';
const REJECTED = 'REJECTED';

// 解析 Promise
const resolvePromise = (promise2, x, resolve, reject) => {
  // 1.先判断 promise2 和 x 是否为同一个实例，也就是调用 then 的时候直接 return promise2，造成 Promise 链循环
  // const promise = Promise.resolve().then(() => { return promise; }) 报错：TypeError: Chaining cycle detected for promise #<Promise>
  // console.log(promise2, x, promise2 === x);
  if (promise2 === x) {
    throw new TypeError('Chaining cycle detected for promise #<Promise>');
  }
  // 2.检测 x 的数据类型，分为三类处理：普通值、普通对象、函数
  if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    // 3.防止会同时触发或者再次触发 resolve 和 error 所以需要加一个标志控制
    let called;
    // 4.1先获取 x.then，获取时可能抛异常，所以需要 try catch 捕获
    // 报错的情形为使用 Object.defineProperty(x, 'then', {get() {throw new Error}})，在 get 中抛异常的极端情况
    try {
      let then = x.then;
      // 5.1 判断 then 是否为一个函数，使用 call 去调用 then 方法，是为了不用再重新取一次 x.then 出现报错的极端情形，y 表示成功回调，r 表示失败回调
      if (typeof then === 'function') {  // 如果 then 拿到一个方法，x 就视为一个 Promise
        then.call(x, y => {
          // 6.成功回调，但是 y 还可能是一个 promise，此时递归 resolvePromise 方法，直到解析出来的值是普通值
          if (called) return;
          called = true;
          resolvePromise(promise2, y, resolve, reject); // 采用 Promise 的成功结果将值向下传递
        }, r => {
          if (called) return;
          called = true;
          reject(r);   // 采用失败的结果向下传递
        })
      } else {
        // 5.2 此时 x 可能是一个对象 {then:1}，x.then 为 undefined 或者一个普通值，直接作为成功的结果往下传
        resolve(x);
      }
    } catch (e) {
      // 4.2捕获异常直接抛出，并且把 called 标志设置为 true，表示已经触发了 reject
      if (called) return;
      called = true;
      reject(e);  // 把报错作为失败的结果往下传
    }
  } else {
    // x 是普通值，基本数据类型，直接把 x 作为 this.result 返回
    resolve(x);
  }
}

class Promise {
  constructor(executor) {
    // 设置默认状态、成功的值、失败的原因
    this.status = PENDING;
    this.result = undefined;
    this.reason = undefined;

    // 定义接收成功回调的数组、失败回调的数组
    // 目的是为解决 executor 中把 resolve/reject 放到定时器宏任务中，导致 promise 实例状态一直是 PENDING
    this.onFulfilledCbs = [];
    this.onRejectedCbs = [];

    // 成功函数，只有当前状态为 pending，才允许修改状态及成功返回值
    let resolve = (value) => {
      if (this.status === PENDING) {
        this.result = value;
        this.status = RESOLVED;
        // 状态改变之后，需要执行成功的回调 onFulfilledCbs，这时候已经拿到了新的 result
        this.onFulfilledCbs.forEach(fn => fn());
      }
    }
    // 成功函数，只有当前状态为 pending，才允许修改状态及失败原因
    let reject = (error) => {
      if (this.status === PENDING) {
        this.reason = error;
        this.status = REJECTED;
        // 状态改变之后，需要执行失败的回调 onRejectedCbs，这时候已经拿到了新的 reason
        this.onRejectedCbs.forEach(fn => fn());
      }
    }

    // 默认执行器立即执行，但是需要捕获执行器中的报错，如果报错等价于执行 reject() 函数
    try {
      executor(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }

  // then 传入两个参数：成功回调 onFulfilled 失败回调 onRejected
  then(onFulfilled, onRejected) {
    // 1.首先判断这两个可选参数是不是 function，不是函数需要转换成函数形式
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val;
    onRejected = typeof onRejected === 'function' ? onRejected : err => {
      throw err
    };

    // 2.实现调用 then 返回一个新的 Promise，并且把前一个 then 调用结果作为 promise2 的状态，promise2 会立即执行
    let promise2 = new Promise((resolve, reject) => {
      // 抽取一个方法：参数为调用的回调函数和回调函数参数 比如 onFulfilled(this.result)
      const asyncToResolvePromise = (fn, param) => {
        setTimeout(() => {
          try {
            let x = fn(param);
            resolvePromise(promise2, x, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      }

      // promise1(也就是 then 的调用者) 状态为 RESOLVED
      if (this.status === RESOLVED) {
        // 3.为了保证此时的 promise2 已经 new 完了，需要使用异步宏任务 setTimeout
        // 注：setTimeout 第二个参数设置为0，表示尽快执行，但是 mdn 解释这个时间间隔至少 >= 4ms
        // setTimeout(() => {
        //   try {
        //     // 在 onFulfilled/resolvePromise 内部执行可能报错，需要捕获异常
        //     let x = onFulfilled(this.result);
        //     // 4.解析 Promise，根据 x 的值(可能是普通值/Promise) 来得到 promise2 的状态
        //     resolvePromise(promise2, x, resolve, reject);
        //   } catch (e) {
        //     // 捕获到异常，直接调用 reject 回调函数把错误信息返回
        //     reject(e);
        //   }
        // }, 0);
        asyncToResolvePromise(onFulfilled, this.result);
      }
      // promise1(也就是 then 的调用者) 状态为 REJECTED
      if (this.status === REJECTED) {
        // setTimeout(() => {
        //   try {
        //     let x = onRejected(this.reason);
        //     resolvePromise(promise2, x, resolve, reject);
        //   } catch (e) {
        //     reject(e);
        //   }
        // }, 0);
        asyncToResolvePromise(onRejected, this.reason);
      }
      // executor 中存在计时器(宏任务) 时会出现 PENDING 状态
      if (this.status === PENDING) {
        // 如果是异步就先订阅
        this.onFulfilledCbs.push(() => {
          // 可能收集成功回调时需要做其他事情 todo...
          asyncToResolvePromise(onFulfilled, this.result);
        })
        this.onRejectedCbs.push(() => {
          asyncToResolvePromise(onRejected, this.reason);
        })
      }
    })
    // 返回一个新的 Promise 实例
    return promise2;
  }
}

// 使用 Promise A+ 规范的验证库来验证当前 Promise 是否符合规范
// 全局安装 promises-aplus-tests 库
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve, reject) => {
    dfd.resolve = resolve
    dfd.reject = reject
  })
  return dfd
}

module.exports = Promise